namespace Angelus.Oracle.Entity.OracleClient.OracleGen
{
    using System;
    using System.Data.Common;
    using System.Data.Metadata.Edm;
    using System.Diagnostics;
    using System.Reflection;
    using System.Xml;
    using Common;

    /// <summary>
    /// Description of OracleProviderManifest.
    /// </summary>
    internal class OracleProviderManifest : DbXmlEnabledProviderManifest
    {
        #region Constants

        /// <summary>
        /// 
        /// </summary>
        private const string ProviderManifestResource = "Angelus.Oracle.Entity.Resources.ProviderManifest.xml";

        /// <summary>
        /// 
        /// </summary>
        private const string StoreSchemaDefinitionResource =
            "Angelus.Oracle.Entity.Resources.StoreSchemaDefinition.ssdl";

        /// <summary>
        /// 
        /// </summary>
        private const string StoreSchemaMappingResource = "Angelus.Oracle.Entity.Resources.StoreSchemaMapping.msl";

        #endregion

        #region Fields

        private string _providerName;
        private OracleVersion _version;

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="manifestToken"></param>
        public OracleProviderManifest(string manifestToken)
            : base(GetProviderManifest())
        {
        }

        #endregion

        #region DbXmlEnabledProviderManifest Overrides

        /// <summary>
        /// 
        /// </summary>
        /// <param name="edmType"></param>
        /// <returns></returns>
        public override TypeUsage GetStoreType(TypeUsage edmType)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="informationType"></param>
        /// <returns></returns>
        protected override XmlReader GetDbInformation(string informationType)
        {
            if (StoreSchemaDefinition == informationType)
            {
                return GetStoreSchemaDescription();
            }

            if (StoreSchemaMapping == informationType)
            {
                return GetStoreSchemaMapping();
            }

            if (ConceptualSchemaDefinition == informationType)
            {
                throw Error.ProviderIncompatible();
            }
            return null;
        }

        /// <summary>
        /// This method takes a type and a set of facets and returns the best mapped equivalent type
        /// in EDM.
        /// </summary>
        /// <param name="storeType">A TypeUsage encapsulating a store type and a set of facets.</param>
        /// <returns>A TypeUsage encapsulating an EDM type and a set of facets.</returns>
        public override TypeUsage GetEdmType(TypeUsage storeType)
        {
            if (storeType == null)
            {
                throw Error.ArgumentNull("storeType");
            }

            string storeTypeName = storeType.EdmType.Name.ToLowerInvariant();
            if (!StoreTypeNameToEdmPrimitiveType.ContainsKey(storeTypeName))
            {
                throw new ArgumentException(string.Format("The underlying provider does not support the type '{0}'.",
                                                          storeTypeName));
            }

            PrimitiveType edmPrimitiveType = StoreTypeNameToEdmPrimitiveType[storeTypeName];

            int maxLength = 0;
            bool isUnicode = true;
            bool isFixedLen;
            bool isUnbounded;

            PrimitiveTypeKind newPrimitiveKind;

            switch (storeTypeName)
            {
                // for some types we just go with simple type usage with no facets
                case "bool":
                case "int":
                case "int16":
                case "int32":
                case "int64":
                case "double":
                case "decimal":
                    return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType);

                case "ref cursor":
                    return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, true, 12345);

                case "varchar2":
                    newPrimitiveKind = PrimitiveTypeKind.String;
                    isUnbounded = !MetadataHelpers.TryGetMaxLength(storeType, out maxLength);
                    isUnicode = false;
                    isFixedLen = false;
                    break;

                case "char":
                    newPrimitiveKind = PrimitiveTypeKind.String;
                    isUnbounded = !MetadataHelpers.TryGetMaxLength(storeType, out maxLength);
                    isUnicode = false;
                    isFixedLen = true;
                    break;

                case "nvarchar2":
                    newPrimitiveKind = PrimitiveTypeKind.String;
                    isUnbounded = !MetadataHelpers.TryGetMaxLength(storeType, out maxLength);
                    isUnicode = true;
                    isFixedLen = false;
                    break;

                case "nchar":
                    newPrimitiveKind = PrimitiveTypeKind.String;
                    isUnbounded = !MetadataHelpers.TryGetMaxLength(storeType, out maxLength);
                    isUnicode = true;
                    isFixedLen = true;
                    break;

                case "clob":
                    newPrimitiveKind = PrimitiveTypeKind.String;
                    isUnbounded = true;
                    isUnicode = false;
                    isFixedLen = false;
                    break;

                case "nclob":
                    newPrimitiveKind = PrimitiveTypeKind.String;
                    isUnbounded = true;
                    isUnicode = true;
                    isFixedLen = false;
                    break;

                case "blob":
                    newPrimitiveKind = PrimitiveTypeKind.Binary;
                    isUnbounded = true;
                    isFixedLen = false;
                    break;

                case "raw":
                    newPrimitiveKind = PrimitiveTypeKind.Binary;
                    isUnbounded = !MetadataHelpers.TryGetMaxLength(storeType, out maxLength);
                    isFixedLen = false;
                    break;

                case "long raw":
                    newPrimitiveKind = PrimitiveTypeKind.Binary;
                    isUnbounded = !MetadataHelpers.TryGetMaxLength(storeType, out maxLength);
                    isFixedLen = false;
                    break;

                case "rowid":
                case "urowid":
                    newPrimitiveKind = PrimitiveTypeKind.String;
                    isUnbounded = !MetadataHelpers.TryGetMaxLength(storeType, out maxLength);
                    isUnicode = true;
                    isFixedLen = true;
                    break;

                case "guid":
                case "binary_float":
                case "binary_double":
                    return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType);

                case "date":
                    return
                        TypeUsage.CreateDateTimeTypeUsage(
                            PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.DateTime), null);

                case "timestamp":
                    return
                        TypeUsage.CreateDateTimeOffsetTypeUsage(
                            PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.DateTimeOffset), null);

                default:
                    throw new NotSupportedException(
                        string.Format("The underlying provider does not support the type '{0}'.", storeTypeName));
            }

            Debug.Assert(PrimitiveTypeKind.String == newPrimitiveKind || PrimitiveTypeKind.Binary == newPrimitiveKind,
                         "at this point only string and binary stype should be present.");

            switch (newPrimitiveKind)
            {
                case PrimitiveTypeKind.String:
                    if (!isUnbounded)
                    {
                        return TypeUsage.CreateStringTypeUsage(edmPrimitiveType, isUnicode, isFixedLen, maxLength);
                    }

                    return TypeUsage.CreateStringTypeUsage(edmPrimitiveType, isUnicode, isFixedLen);

                case PrimitiveTypeKind.Binary:
                    if (!isUnbounded)
                    {
                        return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, isFixedLen, maxLength);
                    }

                    return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, isFixedLen);

                default:
                    throw new NotSupportedException(
                        string.Format("The underlying provider does not support the type '{0}'.", storeTypeName));
            }
        }

        #endregion

        #region Private Implementation

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private static XmlReader GetStoreSchemaDescription()
        {
            return GetXmlResource(StoreSchemaDefinitionResource);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private static XmlReader GetStoreSchemaMapping()
        {
            return GetXmlResource(StoreSchemaMappingResource);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private static XmlReader GetProviderManifest()
        {
            return GetXmlResource(ProviderManifestResource);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="resourceName"></param>
        /// <returns></returns>
        private static XmlReader GetXmlResource(string resourceName)
        {
            if (string.IsNullOrEmpty(resourceName))
            {
                throw new ArgumentNullException(resourceName);
            }

            return XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName), null,
                                    resourceName);
        }

        #endregion
    }
}